package com.ydlclass.loadbalancer.SelectorImpl;

import ch.qos.logback.core.joran.conditional.IfAction;
import com.ydlclass.Transport.message.Request.YrpcRequest;
import com.ydlclass.YrpcBootstrap;
import com.ydlclass.loadbalancer.Selector;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 一致性hash选择器
 * @Author duWenJian
 * @Date 2024/2/26 12:49
 * @Version 1.0
 */
@Slf4j
public class ConsistentHashSelector implements Selector {

    /**
     * 一致性hash环
     */
    private SortedMap<Integer,InetSocketAddress> circle = new TreeMap<>();
    /**
     * 虚拟节点个数
     */
    private int virtualNodeCount;

    public ConsistentHashSelector(List<InetSocketAddress> list , int virtualNodeCount) {
        // 将节点转化为虚拟节点，然后进行挂载
        this.virtualNodeCount = virtualNodeCount;
        for (InetSocketAddress inetSocketAddress : list) {
            // 需要把每一个节点加入到hash环上
            addNodeToCircle(inetSocketAddress);
        }
    }

    @Override
    public InetSocketAddress getNext() {
        // 1、hash环已经建立好了,接下来需要对请求的要素进行处理
        //    有没有办法可以获取到具体的请求内容，yrpcRequest---可以使用ThreadLocal
        YrpcRequest yrpcRequest = YrpcBootstrap.REQUEST_THREAD_LOCAL.get();

        // 我们想根据请求的一些特征来进行选择服务器  id
        String requestId = Long.toString(yrpcRequest.getRequestId());

        // 根据id,进行hash运算,hash应该是极具散列的hash，完全分布在各个地方，所以需要重写
        int hash = hash(requestId);

        // 判断一下该hash是否能直接在hash环上的服务器上，如果能直接返回该服务器 ，如果不包含，则需要寻找下一个节点
        if (!circle.containsKey(hash)) {
            // tailMap返回大于等于key的值顺序组成的map
            SortedMap<Integer, InetSocketAddress> tailMap = circle.tailMap(hash);
            //  看tailmap是否为空，如果为空说明后面没有比他大的了，一个环，返回第一个，如果不为空返回下一个
            hash = tailMap == null ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }


    /**
     * 将每个节点挂载到hash环上
     * @param inetSocketAddress 节点
     */
    private void addNodeToCircle(InetSocketAddress inetSocketAddress) {
        // 为每个节点生成匹配的虚拟节点进行挂载
        for (int i = 0; i < virtualNodeCount; i++) {
            // 生成hash值
            int hash = hash(inetSocketAddress.toString() + "-" + i);
            // 挂载到hash环上
            circle.put(hash, inetSocketAddress);
            if(log.isDebugEnabled()){
                log.debug("hash为[{}]的节点已经挂载到了哈希环上.",hash);
            }
        }
    }

    private void removeNodeToCircle(InetSocketAddress inetSocketAddress) {
        // 从hash环上移除
        for (int i = 0; i < virtualNodeCount; i++) {
            int hash = hash(inetSocketAddress.toString() + "-" + i);
            circle.remove(hash);
        }
    }

//    /**
//     * hash算法 todo 待优化 无法生成均匀的hash值
//     * @param s
//     * @return
//     */
//    private int hash(String s) {
//        MessageDigest md;
//        try {
//            md = MessageDigest.getInstance("MD5");
//        } catch (NoSuchAlgorithmException e) {
//            throw new RuntimeException(e);
//        }
//        byte[] digest = md.digest(s.getBytes());
//        // md5得到的结果是一个字节数组，但是我们想要int 4个字节
//
//        int res = 0;
//        for (int i = 0; i < 4; i++) {
//            res = res << 8;
//            if( digest[i] < 0 ){
//                res = res | (digest[i] & 255);
//            } else {
//                res = res | digest[i];
//            }
//        }
//        return res;
//    }

    private int hash(String s){
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        byte[] bytes = md.digest(s.getBytes());

        int res = 0;
        for (int i = 0; i < 4; i++) {
            res = res << 8;
            res = res | (bytes[i] & 0xFF);
        }
        // 哈希桶数量
        int numBuckets = 1000;
        return Math.floorMod(res, numBuckets);
    }

    private String toBinary(int i){
        String s = Integer.toBinaryString(i);
        int index = 32 -s.length();
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < index; j++) {
            sb.append(0);
        }
        sb.append(s);
        return sb.toString();
    }
}
